//! Card class definition

#include "include/Card.h"
#include <assert.h>
#include <iostream>
#include <QVector>
#include "time.h"
#include "include/Common.h"
#include <QDebug>
#include <algorithm>
#include <functional>

#include <QDataStream>
#include <iterator>

using namespace std;


namespace
{

QDataStream& operator<<(QDataStream& stream, time_t time)
{
	return (stream << (quint64)time);
}

QDataStream& operator>>(QDataStream& stream, time_t& time)
{
	quint64 tmp;
	stream >> tmp;
	time = tmp;

	return stream;
}

}


Card::Card():
	m_question(""),
	m_answer(""),
	m_isNull(true),
	m_persistent(false)
{
}

Card::~Card()
{
//	assert(false);
}

//QDataStream &operator<<( QDataStream &stream, const Card &data )
//{
//	stream << data.m_Uuid;
//	stream << data.m_question;
//	stream << data.m_answer;
//	stream << data.m_questionAudio;
//	stream << data.m_answerAudio;
//	stream << data.m_displayedDate;
//	stream << data.m_remembered;
//	stream << data.m_persistent;
//	return stream;
//}



void Card::readVer1(QDataStream& stream)
{
	m_isNull = false;
	stream >> m_Uuid;
	stream >> m_question;
	stream >> m_answer;
	stream >> m_questionAudio;
	stream >> m_answerAudio;

	QVector<int> tmp;
	stream >> tmp;

	m_displayedDate.resize(tmp.size());
	std::copy(tmp.begin(), tmp.end(), m_displayedDate.begin());

	stream >> m_remembered;
	stream >> m_persistent;
}

void Card::readVer2(QDataStream& stream)
{
	m_isNull = false;
	stream >> m_Uuid;
	stream >> m_question;
	stream >> m_answer;
	stream >> m_questionAudio;
	stream >> m_answerAudio;
	stream >> m_displayedDate;
	stream >> m_remembered;
	stream >> m_persistent;
	stream >> m_timeTaken;

//	if (!m_displayedDate.isEmpty())
//	{
//		std::cout << "display date: ";
//		std::copy(m_displayedDate.begin(), m_displayedDate.end(), std::ostream_iterator<time_span_ms>(std::cout, ", "));
//		std::cout << std::endl;
//	}

//	if (!m_timeTaken.isEmpty())
//	{
//		std::cout << "timeTaken: ";
//		std::copy(m_timeTaken.begin(), m_timeTaken.end(), std::ostream_iterator<time_span_ms>(std::cout, ", "));
//		std::cout << std::endl;
//	}
}

//QDataStream &operator>>( QDataStream &stream, Card &data )
//{
//	data.m_isNull = false;
//	stream >> data.m_Uuid;
//	stream >> data.m_question;
//	stream >> data.m_answer;
//	stream >> data.m_questionAudio;
//	stream >> data.m_answerAudio;
//	stream >> data.m_displayedDate;
//	stream >> data.m_remembered;
//	stream >> data.m_persistent;
//	return stream;
//}

void Card::writeCurrentVer(QDataStream& stream) const
{
	stream << m_Uuid;
	stream << m_question;
	stream << m_answer;
	stream << m_questionAudio;
	stream << m_answerAudio;
	stream << m_displayedDate;
	stream << m_remembered;
	stream << m_persistent;
	stream << m_timeTaken;
}

void Card::setQuestion(const QString& aRQ)
{
	m_isNull = false;
	m_question = aRQ;
}

void Card::setAnswer(const QString& aRA)
{
	m_isNull = false;
	m_answer = aRA;
}

int Card::getTimesAnsweredCorrectly() const
{
	int total = 0;
	for(int i = 0; i < m_remembered.size(); i++)
		total += m_remembered[i] == true ? 1 : 0;
	return total;
}

int	Card::getTimesAnsweredCorrectlyLastInSuccession() const
{
	int a = 0;
	for (int i = m_remembered.size()-1; i >= 0; i--)
	{
		if (m_remembered[i])
			a++;
		else
			break;
	}
	return a;
}

int Card::getTimesDisplayed(time_t atTime) const
{
	if (atTime == -1)
		return m_displayedDate.size();
	else
	{
		typedef TimeContainer::const_iterator CIt;
		typedef TimeContainer::value_type Time;

		CIt it = std::find_if(
					m_displayedDate.begin(),
					m_displayedDate.end(), std::bind2nd(std::greater<Time>(), atTime));

		return it != m_displayedDate.end()
				? std::distance(m_displayedDate.begin(), it) : m_displayedDate.size();

//		int timesDisplayed = 0;
//		for (int i = 0; i < m_displayedDate.size(); i++)
//		{
//			if (m_displayedDate[i] <= atTime)
//				timesDisplayed++;
//			else
//				break;
//		}
//		return timesDisplayed;
	}
}

int Card::getLastDisplayed() const
{
	return m_displayedDate.size() > 0 ? m_displayedDate.back() : -1;
}

int Card::getFirstDisplayed() const
{
	return m_displayedDate.size() > 0 ? m_displayedDate.front() : -1;
}

int Card::getAge() const
{
	if (m_displayedDate.size() == 0)
		return -1;

	return getLastDisplayed() - getFirstDisplayed();
}

bool Card::isDue(long int t) const
{
	if (isNew() == true)
		return true;

	if (t == 0)
		t = (long int)time(0);

	return getDueDate() <= t;
}

int	Card::getDueDate() const
{
	bool lastWasSuccess = getLastDisplayWasSuccess();
	bool successAtLeastOnce = getWasSuccessfullyAnsweredAtLeastOnce();

	// how many times was I remembered?
	// i.e in which slot am I?
	int slot = getSlot();
	// 5 seconds, 25 seconds, 2 minutes, 10 minutes, 1 hour, 5 hours, 1 day, 5 days, 25 days, 4 months, 2 years

	QVector<unsigned int> sslots(14);

	sslots[0] = 0; // 0 seconds
	sslots[1] = 5; // 5 seconds
	sslots[2] = 2*SECONDS_IN_1_MINUTE; // 2 minutes
	sslots[3] = 10*SECONDS_IN_1_MINUTE; // 10 minutes
	sslots[4] = SECONDS_IN_1_HOUR; // 1 hour
	sslots[5] = 5*SECONDS_IN_1_HOUR; // 5 hours
	sslots[6] = SECONDS_IN_1_DAY; // 1 day
	sslots[7] = 5*SECONDS_IN_1_DAY; // 5 days
	sslots[8] = 25*SECONDS_IN_1_DAY; // 25 days
	sslots[9] = 4*SECONDS_IN_1_MONTH; // 4 months
	sslots[10] = 2*SECONDS_IN_1_YEAR; // 2 years
	sslots[11] = 10*SECONDS_IN_1_YEAR; // 10 years
	sslots[12] = 50*SECONDS_IN_1_YEAR; // 50 years

	unsigned int pimsleurDelay = sslots[slot];

	// get the last time the card was successfully answered


	if (successAtLeastOnce && !lastWasSuccess)
	{
		int dueDate = -1;
		return dueDate;
	}

	else if (successAtLeastOnce && lastWasSuccess)
	{
		int dueDate = m_displayedDate.back() + pimsleurDelay;
		return dueDate;
	}

	return -1;
}

bool Card::getLastDisplayWasSuccess() const
{
	if (m_remembered.size() == 0)
		return false;
	return m_remembered[m_remembered.size()-1];
}


bool Card::getWasSuccessfullyAnsweredAtLeastOnce(int atTime) const
{
	for (int i = 0; i < m_displayedDate.size(); i++)
	{
		if (m_remembered[i] == true && (atTime == -1 || m_displayedDate[i] <= atTime))
			return true;
	}
	return false;
}

bool Card::isNew() const
{
	return getWasSuccessfullyAnsweredAtLeastOnce() == false;
}

unsigned int	Card::getNumTimesViewed() const
{
	return m_remembered.size();
}

int Card::getSlot() const
{
	return getTimesAnsweredCorrectlyLastInSuccession();
}

QString Card::getQuestion() const
{
	return m_question;
}

QString Card::getAnswer() const
{
	return m_answer;
}

bool Card::Comp::operator()(Card a1, Card a2)
{
	return a1.getDueDate() < a2.getDueDate();
}

bool Card::DisplayEase::operator()(Card a1, Card a2)
{
	return a1.getDisplayEase() > a2.getDisplayEase();
}

bool Card::AnswerEase::operator()(Card a1, Card a2)
{
	/*	if (a1.m_persistent && !a2.m_persistent)
			return true;
		if (a2.m_persistent && !a1.m_persistent)
			return true;*/

	return a1.getAnswerEase() > a2.getAnswerEase();
}

/*When a card is displayed to the user, i.e when the question is flashed
this function is called with true to keep track of that fact. If for some reason
the card was not answered (neither correctly or incorrectly) this function
needs to be called with false so it doesn't get marked as badly answered.
*/
void Card::setDisplayed(bool displayed)
{
	if (displayed)
	{
		m_displayedDate.push_back(time(0));
		m_remembered.push_back(false);
	}
	else
	{
		assert(m_remembered.size() != 0);
		m_displayedDate.pop_back();
		m_remembered.pop_back();
	}
}


void Card::setKnewIt()
{
	assert(m_remembered.size() != 0);
	m_remembered.back() = true;
	m_displayedDate.back() = time(0);
}


int Card::getTimesAnsweredWrong() const
{
	// if the card was never known, then return 0
	if (getWasSuccessfullyAnsweredAtLeastOnce())
	{
		bool answeredCorrectlyAtLeastOnce = false;
		unsigned int count = 0;
		// for each answer
		for (int i = 0; i < m_remembered.size(); i++)
		{
			// once we've answered at least once
			if (answeredCorrectlyAtLeastOnce == false)
				if (m_remembered[i] == true)
					answeredCorrectlyAtLeastOnce = true;

			if (answeredCorrectlyAtLeastOnce && m_remembered[i] == false)
			{
				//count num times answered wrong
				count++;
			}
		}
		return count;
	}
	else
		return 0;
}

float Card::getDisplayEase(int atTime) const
{
	if (getTimesDisplayed(atTime) == 0)
		return 1.0;
	else
		return 1.0/getTimesDisplayed(atTime);
}

float Card::getAnswerEase(int /*atTime*/) const
{
	if (getWasSuccessfullyAnsweredAtLeastOnce() == false)
		return 1.0;
	else
	{
		int timesAnsweredWrong = getTimesAnsweredWrong();
		int timesAnsweredCorrectly = getTimesAnsweredCorrectly();

		int total = timesAnsweredCorrectly + timesAnsweredWrong;

		if (timesAnsweredWrong == 0)
			return 1.0;
		return timesAnsweredCorrectly/(float)total;
	}
}

void Card::reset()
{
	//TimeContainer().swap(m_displayedDate); //STL compatible solution
	m_displayedDate.clear();
	m_remembered.clear();
	m_timeTaken.clear();
}

bool Card::isNull() const
{
	return m_isNull;
}

QUuid Card::getQuestionAudio() const
{
	return m_questionAudio;
}

QUuid Card::getAnswerAudio() const
{
	return m_answerAudio;
}

void Card::setQuestionAudio(QUuid uuid)
{
	m_questionAudio = uuid;
}

void Card::setAnswerAudio(QUuid uuid)
{
	m_answerAudio = uuid;
}

void Card::setIsPersistent(bool b)
{
	m_persistent = b;
}

bool Card::getIsPersistent() const
{
	return m_persistent;
}

void Card::print()
{
	qDebug() << m_question << endl;
	qDebug() << m_answer << endl;

}


void Card::registerTimeToAnswerMs(time_span_ms durationMs)
{
	m_timeTaken.push_back(durationMs);
}

